package com.jgp.security.jpa.filters;

import com.jgp.common.annotation.DataPower;
import com.jgp.common.annotation.PowerForType;
import com.jgp.common.persistence.Model;
import com.jgp.security.config.SecUserAdapter;
import com.jgp.security.pojo.UserInfo;
import com.jgp.security.secmodel.SecInstitution;
import com.jgp.security.secmodel.SecRole;
import com.jgp.security.service.SecPowerService;
import com.jgp.sys.query.jpa.DataFilter;
import com.jgp.sys.query.power.DataPowerConfig;
import com.jgp.sys.query.power.DataPowerOperator;
import com.jgp.sys.utils.SpringContextUtil;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 项目   jgp-cloud-parent
 * 作者   loufei
 * 时间   2019-11-10
 */
public class DataPermissionFilter implements DataFilter {
    
    @Override
    public Predicate doPredicate(Root<? extends Model> root, CriteriaQuery<?> query, CriteriaBuilder builder, DataPowerConfig powerConfig) {
        List<Predicate> predicates = new ArrayList<>();
        if(!powerConfig.isEnabled()){
            return null;
        }
        SecUserAdapter userAdapter = SpringContextUtil.getBean(SecUserAdapter.class);
        if (Objects.isNull(userAdapter)) {
            return null;
        }
        
        UserInfo userInfo = userAdapter.currentUser();
        List<SecInstitution> institutions = userInfo.getInstitutions();
        List<SecRole> roles = userInfo.getRoles();
        
        if (Objects.nonNull(powerConfig)) {
            List<DataPowerConfig.Group> groups = powerConfig.getGroups();
            List<Predicate> psx = new ArrayList<>();
            groups.forEach(group -> {
                DataPowerOperator operator = group.getOperator();
                List<Predicate> ps = new ArrayList<>();
                group.getItems().forEach(item -> {
                    Predicate predicate = predicate(item.getType(), item.getCode(), userInfo, institutions, roles, root, query, builder);
                    if(Objects.nonNull(predicate)) ps.add(predicate);
                });
                if(ps.size()>0){
                    if (DataPowerOperator.AND.equals(operator)) {
                        psx.add(builder.and(ps.toArray(new Predicate[ps.size()])));
                    } else {
                        psx.add(builder.or(ps.toArray(new Predicate[ps.size()])));
                    }
                }
                
            });
            return psx.size()>0?builder.and(psx.toArray(new Predicate[psx.size()])):null;
        } else {
            DataPower[] powers = root.getJavaType().getAnnotationsByType(DataPower.class);
            if (Objects.isNull(powers) || powers.length == 0) {
                return null;
            }
            
            List<DataPower> ps = Arrays.asList(powers).stream().distinct().collect(Collectors.toList());
            
            for (DataPower power : ps) {
                Predicate predicate = predicate(power.value(), null, userInfo, institutions, roles, root, query, builder);
                if(Objects.nonNull(predicate)) predicates.add(predicate);
            }
            
            predicates = predicates.stream().filter(Objects::nonNull).collect(Collectors.toList());
        }
        return predicates.size() > 0 ? builder.or(predicates.toArray(new Predicate[predicates.size()])) : null;
    }
    
    private Predicate predicate(PowerForType type, String code, UserInfo userInfo, List<SecInstitution> institutions, List<SecRole> roles, Root<?
            extends Model> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        if (PowerForType.CUSTOM.equals(type)) {
            return customCondition(type, userInfo.getId(), institutions, roles, root, query, builder);
        }
        if (PowerForType.SPECIAL.equals(type)) {
            Map<String,List<SecInstitution>> map = userInfo.getSInsAllMap();
            List<SecInstitution> secInstitutions = map.get(code);
            return specialCondition(secInstitutions, root, query, builder);
        } else {
            return commonCondition(type, userInfo.getId(), institutions, roles, root, query, builder);
        }
    }
    
    private Predicate customCondition(PowerForType type, String userId, List<SecInstitution> institutions, List<SecRole> roles, Root<?
            extends Model> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        return null;
    }
    
    private Predicate specialCondition(List<SecInstitution> secInstitutions, Root<? extends Model> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        Predicate predicate = null;
        if(Objects.nonNull(secInstitutions)&&secInstitutions.size()>0){
            predicate = specialInstitution(root, query, secInstitutions);
        }
        return predicate;
    }
    
    private Predicate specialInstitution(Root<? extends Model> root, CriteriaQuery<?> query, List<SecInstitution> secInstitutions) {
        SecPowerService powerService = SpringContextUtil.getBean(SecPowerService.class);
        List<String> rowIds = powerService.queryRowIdsBySpecialInstitutionsAndDown(secInstitutions);
        if(rowIds.size() > 0){
            return root.get("rowId").in(rowIds);
        }else {
            return null;
        }
       
    }
    
    private Predicate commonCondition(PowerForType type, String userId, List<SecInstitution> institutions, List<SecRole> roles, Root<?
            extends Model> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        Predicate predicate = null;
        switch (type) {
            case ORG:
                if (institutions.size() > 0) {
                    predicate = sameInstitution(root, query, institutions);
                }
                break;
            case ORG_DOWN:
                if (institutions.size() > 0) {
                    predicate = institutionDown(root, query, builder, institutions);
                }
                break;
            case ROLE:
                if (roles.size() > 0) {
                    predicate = sameRole(root, query, roles);
                }
                break;
            case ROLE_DOWN:
                if (roles.size() > 0) {
                    predicate = roleDown(root, query, builder, roles);
                }
                break;
            case ORG_ROLE:
                if (roles.size() > 0 && institutions.size() > 0) {
                    Predicate p0 = sameRole(root, query, roles);
                    Predicate p1 = sameInstitution(root, query, institutions);
                    predicate = builder.and(p0, p1);
                    break;
                }
            case PRIVATE:
                predicate = builder.equal(root.get("rowId"), userId);
                break;
            case CUSTOM:
                break;
            
        }
        return predicate;
    }
    
    private Predicate sameInstitution(Root root, CriteriaQuery<?> query, List<SecInstitution> institutions) {
        /*List<Long> insIds = institutions.stream().map(SecInstitution::getId).collect(Collectors.toList());
        Subquery insUserQuery = query.subquery(String.class);
        Root insUserRoot = insUserQuery.from(SecInstitutionUser.class);
        insUserQuery.select(insUserRoot.get("userId")).where(insUserRoot.get("institutionId").in(insIds));*/
        SecPowerService powerService = SpringContextUtil.getBean(SecPowerService.class);
        return root.get("rowId").in(powerService.queryRowIdsByInstitutions(institutions));
    }
    
    
    private Predicate sameRole(Root root, CriteriaQuery<?> query, List<SecRole> roles) {
        
        SecPowerService powerService = SpringContextUtil.getBean(SecPowerService.class);
        List<String> rowIds = powerService.queryRowIdsByRoles(roles);
        /*  List<Long> roleIds = roles.stream().map(SecRole::getId).collect(Collectors.toList());
        Subquery roleUserQuery = query.subquery(String.class);
        Root insUserRoot = roleUserQuery.from(SecRoleUser.class);
        roleUserQuery.select(insUserRoot.get("userId")).where(insUserRoot.get("roleId").in(roleIds));*/
        return root.get("rowId").in(rowIds);
    }
    
    private Predicate institutionDown(Root root, CriteriaQuery<?> query, CriteriaBuilder builder, List<SecInstitution> institutions) {
        
        SecPowerService powerService = SpringContextUtil.getBean(SecPowerService.class);
        List<String> rowIds = powerService.queryRowIdsByInstitutionsAndDown(institutions);
        
        /*Subquery insUserQuery = query.subquery(String.class);
        Root insUserRoot = insUserQuery.from(SecInstitutionUser.class);
        
        Subquery insQuery = insUserQuery.subquery(SecInstitution.class);
        Root insRoot = insQuery.from(SecInstitution.class);
        List<Predicate> predicates = new ArrayList<>();
        for (SecInstitution ins : institutions) {
            predicates.add(builder.like(insRoot.get("supers"), ins.getSupers() + "%"));
        }
        Predicate where = builder.or(predicates.toArray(new Predicate[predicates.size()]));
        Subquery subquery = insQuery.select(insRoot.get("id")).where(where);
        insUserQuery.select(insUserRoot.get("userId")).where(insUserRoot.get("institutionId").in(subquery));*/
        
        return root.get("rowId").in(rowIds);
    }
    
    private Predicate roleDown(Root root, CriteriaQuery<?> query, CriteriaBuilder builder, List<SecRole> roles) {
        
        /*Subquery roleUserQuery = query.subquery(String.class);
        Root roleUserRoot = roleUserQuery.from(SecRoleUser.class);
        
        Subquery insQuery = roleUserQuery.subquery(SecInstitution.class);
        Root roleRoot = insQuery.from(SecRole.class);
        List<Predicate> predicates = new ArrayList<>();
        for (SecRole role : roles) {
            predicates.add(builder.like(roleRoot.get("supers"), role.getSupers() + "%"));
        }
        Predicate where = builder.or(predicates.toArray(new Predicate[predicates.size()]));
        Subquery subquery = insQuery.select(roleRoot.get("id")).where(where);
        roleUserQuery.select(roleUserRoot.get("userId")).where(roleUserRoot.get("roleId").in(subquery));*/
        SecPowerService powerService = SpringContextUtil.getBean(SecPowerService.class);
        List<String> rowIds = powerService.queryRowIdsByRolesAndDown(roles);
        return root.get("rowId").in(rowIds);
    }
}
